package com.activeharmony.content;

import com.activeharmony.ActiveHarmony;
import com.activeharmony.Cache;
import com.activeharmony.Configuration;
import com.activeharmony.Model;
import com.activeharmony.TableInfo;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.DataAbilityHelper;
import ohos.aafwk.ability.PathMatcher;
import ohos.data.dataability.DataAbilityPredicates;
import ohos.data.rdb.ValuesBucket;
import ohos.data.resultset.ResultSet;
import ohos.utils.PlainArray;
import ohos.utils.net.Uri;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

/**
 * ContentProvider Class
 */
public class ContentProvider extends Ability {
    // ////////////////////////////////////////////////////////////////////////////////////
    // PRIVATE CONSTANTS
    // ////////////////////////////////////////////////////////////////////////////////////
    private static final PathMatcher PATH_MATCHER = new PathMatcher();
    private static final PlainArray<Class<? extends Model>> TYPE_CODES = new PlainArray<Class<? extends Model>>();

    // ////////////////////////////////////////////////////////////////////////////////////
    // PRIVATE MEMBERS
    // ////////////////////////////////////////////////////////////////////////////////////

    private static String sAuthority;
    private static PlainArray<String> sMimeTypeCache = new PlainArray<String>();

    private ContentProvider() {
        onCreate();
    }

    private static class SingletonHelper {
        private static final ContentProvider INSTANCE = new ContentProvider();
    }

    /**
     * getInstance
     *
     * @return ContentProvider
     */
    public static ContentProvider getInstance() {
        return SingletonHelper.INSTANCE;
    }

    /**
     * onCreate
     *
     * @return boolean result
     */
    public boolean onCreate() {
        ActiveHarmony.initialize(getConfiguration());
        sAuthority = getAuthority();

        final List<TableInfo> tableInfos = new ArrayList<TableInfo>(Cache.getTableInfos());
        final int size = tableInfos.size();
        for (int i = 0; i < size; i++) {
            final TableInfo tableInfo = tableInfos.get(i);
            final int tableKey = (i * 2) + 1;
            final int itemKey = (i * 2) + 2;

            // content://<authority>/<table>
            PATH_MATCHER.addPath(tableInfo.getTableName().toLowerCase(Locale.ROOT), tableKey);
            TYPE_CODES.put(tableKey, tableInfo.getType());

            // content://<authority>/<table>/<id>
            PATH_MATCHER.addPath(tableInfo.getTableName().toLowerCase(Locale.ROOT) + "/#", itemKey);
            TYPE_CODES.put(itemKey, tableInfo.getType());
        }

        return true;
    }

    @Override
    public String getType(Uri uri) {
        final int match = PATH_MATCHER. getPathId(uri.toString());

        Optional<String> cachedMimeType = sMimeTypeCache.get(match);
        if (cachedMimeType != null) {
            return cachedMimeType.get();
        }

        final Class<? extends Model> type = getModelType(uri);
        final boolean single = ((match % 2) == 0);

        StringBuilder mimeType = new StringBuilder();
        mimeType.append("vnd");
        mimeType.append(".");
        mimeType.append(sAuthority);
        mimeType.append(".");
        mimeType.append(single ? "item" : "dir");
        mimeType.append("/");
        mimeType.append("vnd");
        mimeType.append(".");
        mimeType.append(sAuthority);
        mimeType.append(".");
        mimeType.append(Cache.getTableName(type));

        sMimeTypeCache.put(match, mimeType.toString());

        return mimeType.toString();
    }

    // SQLite methods

    @Override
    public int insert(Uri uri, ValuesBucket values) {
        final Class<? extends Model> type = getModelType(uri);
        final Long id = Cache.openDatabase().insert(Cache.getTableName(type), null, values);

        if (id != null && id > 0) {
            Uri retUri = createUri(type, id);
            notifyChange(retUri);

            return retUri.hashCode();
        }

        return 0;
    }

    @Override
    public int update(Uri uri, ValuesBucket values, DataAbilityPredicates predicates) {
        final Class<? extends Model> type = getModelType(uri);
        final int count = Cache.openDatabase().update(Cache.getTableName(type), values, predicates.getWhereClause(),
            predicates.getWhereArgs().toArray(new String[0]));

        notifyChange(uri);

        return count;
    }

    @Override
    public int delete(Uri uri, DataAbilityPredicates predicates) {
        final Class<? extends Model> type = getModelType(uri);
        final int count = Cache.openDatabase().delete(Cache.getTableName(type), predicates.getWhereClause(),
            predicates.getWhereArgs().toArray(new String[0]));

        notifyChange(uri);

        return count;
    }

    @Override
    public ResultSet query(Uri uri, String[] projection, DataAbilityPredicates predicate) {
        final Class<? extends Model> type = getModelType(uri);
        final ResultSet cursor =
            Cache.openDatabase()
                .query(Cache.getTableName(type), projection, predicate.getWhereClause(),
                    predicate.getWhereArgs().toArray(new String[0]), null, null, predicate.getOrder());

        cursor.setAffectedByUris(DataAbilityHelper.creator(getContext()), Collections.singletonList(uri));

        return cursor;
    }

    // ////////////////////////////////////////////////////////////////////////////////////
    // PUBLIC METHODS
    // ////////////////////////////////////////////////////////////////////////////////////

    /**
     * createUri
     *
     * @param type type
     * @param id id
     * @return Uri
     */
    public Uri createUri(Class<? extends Model> type, Long id) {
        final StringBuilder uri = new StringBuilder();
        uri.append("content://");
        uri.append(sAuthority);
        uri.append("/");
        uri.append(Cache.getTableName(type).toLowerCase(Locale.ROOT));

        if (id != null) {
            uri.append("/");
            uri.append(id.toString());
        }

        return Uri.parse(uri.toString());
    }

    // ////////////////////////////////////////////////////////////////////////////////////
    // PROTECTED METHODS
    // ////////////////////////////////////////////////////////////////////////////////////

    /**
     * getAuthority
     *
     * @return bundle name
     */
    protected String getAuthority() {
        return getContext().getBundleName();
    }

    /**
     * getConfiguration
     *
     * @return Configuration
     */
    protected Configuration getConfiguration() {
        return new Configuration.Builder(getContext()).create();
    }

    // ////////////////////////////////////////////////////////////////////////////////////
    // PRIVATE METHODS
    // ////////////////////////////////////////////////////////////////////////////////////

    private Class<? extends Model> getModelType(Uri uri) {
        final int code = PATH_MATCHER.getPathId(uri.toString());
        if (code != PathMatcher.NO_MATCH) {
            return TYPE_CODES.get(code).get();
        }

        return null;
    }

    private void notifyChange(Uri uri) {
        DataAbilityHelper.creator(getContext()).notifyChange(uri);
    }
}
